#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>//wait函数:man 2 wait
#include <sys/wait.h>

////flag1
//int fun()
//{
//    printf("call fun function done!\n");
//    
//    //return 1;//2、在其他函数中进行return，表示的是函数调用结束，注意这个不是整个进程的退出码，可以用echo $?查看
//
//    //exit(21);//在任意位置使用exit函数，都是直接终止整个进程，后续的代码不会被执行
//
//    _exit(21);
//}
//int main()
//{
//    ////flag1
//    //fun();
//    //printf("I am a process, pid:%d,ppid:%d\n",getpid(),getppid());
//    ////exit(12);//exit函数的参数是进程的退出码，相当于直接在main函数中return某个数值
//    //_exit(12);
//    //return 12;//1、在main函数中进行直接return
//
//    //printf("Can you see me?\n");//遇到刷新缓冲区的条件：\n
//    printf("Can you see me?");//没有换行符，等到exit被调用才打印，说明此时进程即将终止，操作系统强制刷新缓冲区，所以才会打印
//    //main函数中的return语句也能达到上述刷新缓冲区的效果
//    sleep(3);
//    //exit(1);
//    _exit(1);//不会刷新缓冲区的内容，也就是说那些缓冲区的数据成为垃圾了，这是和exit的其中一个区别
//    //exit在3号手册，是库函数；_exit在2号手册，是系统调用；另外exit的底层实现就是_exit，也就是说本来exit可以刷新缓冲区
//    //那么按道理_exit函数也可以，之所以不行是因为缓冲区不在操作系统内部，而是和exit函数一样在C语言的库中
//}


void Worker(int number)
{
    //int* p = NULL;//和下面的解引用配套使用，测试退出信号
    int cnt = 3;
    //while(1)//测试手动杀死进程时退出信号和退出码的状态，可在新的窗口输入”kill -9 子进程id“
    while(cnt)
    {
        printf("I am child process,pid: %d,ppid: %d,cnt: %d,number: %d\n",getpid(),getppid(),cnt--,number);//number可以看出进程不按照想象的顺序执行
        //*p = 10;//野指针，退出信号为11，如果收到进程异常信号（exit sig不为0），那么退出码（exit code）无意义
        //这就如同考试作弊被发现了，那么不管结果考了多少分都没用，也就是发现异常信号，就不用再去看退出码了
        //综上，退出信号和退出码都为0，代码运行正常，结果准确；退出信号为0，退出码不为0，代码运行正常，结果不准确；
        //退出信号不为0，则是进程运行异常，退出码是什么也没多少用了
        sleep(1);
    }

    //int a = 10;
    //a /= 0;//故意设置错误代码，看看进程异常终止对应的status,退出信号为8（可通过kill -l查看）

}

const int n = 10;//创建n个子进程

int main()
{

    //pid_t id = fork();
    //if(id == 0) //child
    //{
    //    Worker();
    //    exit(10);
    //}
    //else //father
    //{
    //    //sleep(10);//确保子进程已经运行结束，再来调用wait函数
    //    printf("Before waiting\n");//注释上面的sleep语句，可以观察得到父进程先运行了这一句代码
    //    //pid_t rid = wait(NULL);//因为该父进程只有一个子进程，如果成功就返回子进程的pid，这也是为了父进程能够管理子进程
    //    int status = 0;
    //    pid_t rid = waitpid(id, &status, 0);//第三个参数options为0默认为阻塞等待,第二个参数是整形指针，把子进程的退出信息返回
    //    printf("After waiting\n");//等到子进程运行结束了才会执行这条语句，说明父进程会因为wait函数阻塞等待
    //    //直到子进程运行结束变成僵尸状态再由wait自动回收，最后返回子进程的pid
    //    if(rid == id)
    //    {
    //        //printf("wait success, pid: %d,status: %d,rpid: %d,exit sig: %d,exit code: %d\n",
    //        //            getpid(),status,rid,status&0x7F,(status>>8)&0xFF);
    //        //status的后面七位是终止信号（exit sig），低16位的高8位是退出状态（exit code），也就是说status可以拆分成一定位数使用
    //        //所以当if中的exit参数为10，status是2560（0000 1010 0000 0000）
    //        
    //        if(WIFEXITED(status))//实际上可以用这个函数检查退出是否异常，若正常退出则为真
    //        {//Wait if exited?
    //            printf("Child process quit normally,whose exit code: %d\n",WEXITSTATUS(status));//若WIFEXITED不为0，则提取子进程退出码
    //        }//Wait exit status?
    //        else
    //        {
    //            printf("Child process quit abnormally\n");
    //        }
    //    }
    //    sleep(10);
    //}

    
    //等待多个子进程退出
    for(int i = 0;i < n;i++)//创建n个子进程
    {
        pid_t id = fork();
        if(id == 0)
        {
           Worker(i);
           //status = i;//为什么不定义一个全局变量int status = 0;然后在这里赋值后使用呢，而是选择等待子进程退出，再使用特定的函数来获取退出信息：因为进程具有独立性，父进程无法直接获得子进程的退出信息（也就是说进程的数据是独立的，子进程修改了status，但是父进程不知道）
           exit(i);
        }
    }
    for(int i = 0;i < n;i++)
    {
        int status = 0;
        pid_t rid = waitpid(-1,&status,0);//-1表示等待任意一个退出的子进程
        if(rid > 0){
            printf("wait child %d success,exit code: %d\n",rid,WEXITSTATUS(status));
        }
    }


    return 0;

}



